using System;
using System.Collections.Generic;
using TDUnit.Common.Logging;
using TDUnit.Common.ScriptEngine;
using TDUnit.Common.ScriptExecutionEngine;
using TDUnit.Common.TestData;
using TDUnit.Common.TestDataParser;

namespace TDUnit
{
    public class TestDataUtility : IDisposable
    {
        #region Private variables

        private readonly ITestDataParser parser;
        private TestData dataset;
        private readonly IScriptExecutionEngine executionEngine;
        private readonly IScriptEngine scriptEngine;
        private readonly ILogger logger;

        #endregion

        #region Events

        public event EventHandler<TestDataUtilityEventArgs> SettingUpTestData;
        public event EventHandler<TestDataUtilityEventArgs> TestDataSetUp;
        public event EventHandler<TestDataUtilityEventArgs> TearingDownTestData;
        public event EventHandler<TestDataUtilityEventArgs> TestDataTornDown;

        #endregion

        #region Constructors

        public TestDataUtility(ITestDataParser parser, IScriptExecutionEngine executionEngine, IScriptEngine scriptEngine, ILogger logger)
        {
            this.parser = parser;
            this.executionEngine = executionEngine;
            this.scriptEngine = scriptEngine;
            this.logger = logger;
            SetUpEventHandlers();
        }

        #endregion

        #region Public properties

        public IScriptEngine ScriptEngine
        {
            get { return scriptEngine; }
        }

        public IScriptExecutionEngine ScriptExecutionEngine
        {
            get { return executionEngine; }
        }

        #endregion

        #region Public methods

        public TestData SetUp()
        {
            dataset = parser.Parse();
            OnSettingUpTestData();
            try
            {
                CreateTestData();
                OnTestDataSetUp();
                return dataset;
            }
            catch (Exception)
            {
                TearDown();
                throw;
            }
        }

        public void TearDown()
        {
            if (dataset == null)
                dataset = parser.Parse();
            OnTearingDownTestData();
            RemoveTestData();
            OnTestDataTornDown();
        }

        public void Dispose()
        {
            logger.Dispose();
        }

        #endregion

        #region OnEvent methods
        protected virtual void OnSettingUpTestData()
        {
            if (SettingUpTestData != null)
                SettingUpTestData(this, new TestDataUtilityEventArgs(dataset));
        }

        protected virtual void OnTearingDownTestData()
        {
            if (TearingDownTestData != null)
                TearingDownTestData(this, new TestDataUtilityEventArgs(dataset));
        }

        protected virtual void OnTestDataSetUp()
        {
            if (TestDataSetUp != null)
                TestDataSetUp(this, new TestDataUtilityEventArgs(dataset));
        }

        protected virtual void OnTestDataTornDown()
        {
            if (TestDataTornDown != null)
                TestDataTornDown(this, new TestDataUtilityEventArgs(dataset));
        }
        #endregion

        #region Test data methods

        private void CreateTestData()
        {
            CreateTestDataForTables(dataset.Tables);
        }

        private void RemoveTestData()
        {
            RemoveDataForTables(dataset.Tables);
        }

        private void CreateTestDataForTables(IEnumerable<Table> tables)
        {
            foreach (var t in tables)
            {
                InsertRows(t.Rows, t.IsPrimaryKeyAutoIdentity);
            }
        }

        private void InsertRows(IEnumerable<Row> rows, bool isAutoIdent)
        {
            foreach (var r in rows)
            {
                InsertRow(r);
                if (isAutoIdent)
                {
                    r.PrimaryKeyColumn.Value = executionEngine.ExecuteScalarQueryScript(
                        scriptEngine.GetSelectAutoIdentityScript()).ToString();
                }
            }
        }

        private void InsertRow(Row row)
        {
            var insertScript = scriptEngine.GetInsertScriptForRow(row);
            executionEngine.ExecuteNonQueryScript(insertScript);
        }

        private void RemoveDataForTables(Table[] tables)
        {
            Array.Reverse(tables);
            foreach (var t in tables)
            {
                DeleteRows(t.Rows);
            }
        }

        private void DeleteRows(Row[] rows)
        {
            Array.Reverse(rows);
            foreach (var row in rows)
            {
                var script = scriptEngine.GetDeleteScriptForRow(row);
                executionEngine.ExecuteNonQueryScript(script);
            }
        }

        #endregion

        #region Helper methods

        private void SetUpEventHandlers()
        {
            scriptEngine.AutoIdentityScriptCreated += (s, e) => logger.LogMessage("Created AutoIdentity script: " + e.Script, MessageType.Information);
            scriptEngine.DeleteScriptCreated += (s, e) => logger.LogMessage("Created Delete script: " + e.Script, MessageType.Information);
            scriptEngine.InsertScriptCreated += (s, e) => logger.LogMessage("Created Insert script: " + e.Script, MessageType.Information);

            executionEngine.ExecutedNonQueryScript += (s, e) => logger.LogMessage("Executed non-query script: " + e.ScriptToExecute, MessageType.Information);
            executionEngine.ExecutedScalarQueryScript += (s, e) => logger.LogMessage("Executed scalar-query script: " + e.ScriptToExecute, MessageType.Information);

            SettingUpTestData += (s, e) => logger.LogMessage("Setting up test data...", MessageType.Information);
            TearingDownTestData += (s, e) => logger.LogMessage("Tearing down test data...", MessageType.Information);
            TestDataSetUp += (s, e) => logger.LogMessage("Test data has been set up.", MessageType.Information);
            TestDataTornDown += (s, e) =>
            {
                logger.LogMessage("Test data has been torn down.", MessageType.Information);
                logger.Dispose();
            };
        }

        #endregion
    }
}